import { Point2 } from "../point/Point2";
import { Precision } from "../Precision";
import { Vector2 } from "../vector/Vector2";
import { Curve2 } from "./Curve2";
import { Curve2Type } from "./Curve2Type";
import { IntersectionType } from "./IntersectionType";
import { Segment2 } from "./Segment2";


/**
* @description 2D圆弧
* @author xuld
* @date 2024-11-14
* @lastEditTime 2024-11-14 09:38:30
* @lastEditors xuld
*/
export class Arc2 extends Curve2 {
    private _center: Point2;
    private _radius: number;
    private _startAngle: number;
    private _endAngle: number;

    /**
     * @description 构造函数
     * @param startAngle - 起始角度
     * @param endAngle - 终止角度
     * @param center - 圆心
     * @param radius - 半径
     */
    constructor(startAngle: number, endAngle: number, center: Point2, radius: number) {
        super(Curve2Type.Arc);
        this._startAngle = startAngle;
        this._endAngle = endAngle;
        this._center = center.clone();
        this._radius = radius;
    }

    /**
    * @description 克隆曲线
    * @return 克隆的曲线
    */
    public clone(): Arc2 {
        return new Arc2(this._startAngle, this._endAngle, this._center, this._radius);
    }

    /**
     * @description 起点
     * @return 起点
     */
    public get start(): Point2 {
        // 使用参数化方法获取起点
        return new Point2(
            this._center.x + this._radius * Math.cos(this._startAngle),
            this._center.y + this._radius * Math.sin(this._startAngle)
        );
    }

    /**
     * @description 终点
     * @return 终点
     */
    public get end(): Point2 {
        // 使用参数化方法获取终点
        return new Point2(
            this._center.x + this._radius * Math.cos(this._endAngle),
            this._center.y + this._radius * Math.sin(this._endAngle)
        );
    }

    /**
     * @description 圆心
     * @return 圆心
     */
    public get center(): Point2 {
        return this._center;
    }

    /**
     * @description 半径
     * @return 半径
     */
    public get radius(): number {
        return this._radius;
    }

    /**
     * @description 转换为字符串表示
     * @returns 格式化的点字符串
     */
    public toString(): string {
        return `{"type": "Arc2", "startAngle": ${this.startAngle}, "endAngle": ${this.endAngle}, "center": ${this._center.toString()}, "radius": ${this._radius}}`;
    }

    /**
     * @description 圆弧长度
     * @return 弧长
     */
    public get length(): number {
        return this._radius * Math.abs(this.endAngle - this.startAngle);
    }

    /**
     * @description 获取圆弧的起始角度（弧度）
     */
    public get startAngle(): number {
        return this._startAngle;
    }

    /**
     * @description 获取圆弧的终止角度（弧度）
     */
    public get endAngle(): number {
        return this._endAngle;
    }

    /**
    * @description 求曲线在参数 u 处的坐标
    * @param u - 参数方程的参数 (0-1之间)
    * @return 坐标
    */
    public d0(u: number): Point2 {
        // 将 u (0-1) 映射到角度范围
        const angle = this._startAngle + (this._endAngle - this._startAngle) * u;
        return new Point2(
            this._center.x + this._radius * Math.cos(angle),
            this._center.y + this._radius * Math.sin(angle)
        );
    }

    /**
     * 求曲线在参数 u 处的坐标和切线
     * @param u - 参数方程的参数
     * @return 坐标和切线
     */
    public d1(u: number): { point: Point2, tangent: Vector2 } {
        const point = this.d0(u);
        // 切线垂直于半径方向
        const radialVector = this._center.vectorTo(point);
        const tangent = new Vector2(-radialVector.y, radialVector.x).normalize();
        return { point, tangent };
    }

    /**
    * @description 点是否在线内
    * @param p - 点
    * @param epsilon - 点在线上的误差, 默认 0.00001
    * @return true 点在线上，false 点不在线上
    */
    public containPoint(p: Point2, epsilon: number = Precision.EPSILON5): boolean {
        // 1. 严格检查点到圆心的距离是否等于半径
        const distToCenter = p.distance(this._center);
        // 使用相对误差来判断距离
        const relativeError = Math.abs(distToCenter - this._radius) / this._radius;
        if (relativeError > epsilon) {
            return false;
        }

        // 2. 检查点是否在圆弧的角度范围内
        // 计算点相对于圆心的角度，范围为 [-π, π]
        let pointAngle = Math.atan2(p.y - this._center.y, p.x - this._center.x);

        // 将所有角度标准化到 [0, 2π] 范围内
        let startAngle = this._startAngle % (2 * Math.PI);
        let endAngle = this._endAngle % (2 * Math.PI);
        if (startAngle < 0) startAngle += 2 * Math.PI;
        if (endAngle < 0) endAngle += 2 * Math.PI;
        if (pointAngle < 0) pointAngle += 2 * Math.PI;

        // 处理跨越0度线的情况
        if (endAngle < startAngle) {
            endAngle += 2 * Math.PI;
            if (pointAngle < startAngle) {
                pointAngle += 2 * Math.PI;
            }
        }

        // 使用角度误差来判断点是否在角度范围内
        const angleEpsilon = epsilon;
        if (pointAngle < startAngle - angleEpsilon || pointAngle > endAngle + angleEpsilon) {
            return false;
        }

        return true;
    }

    /**
    * @description 求点 point 到曲线的最近投影点
    * @param point - 点
    * @return 最近的投影点及其u值
    */
    public projectPoint(point: Point2): { point: Point2, u: number } | undefined {
        // 计算点到圆心的向量
        const toPoint = this._center.vectorTo(point);
        // 归一化后乘以半径得到在圆上的投影点
        const projectedPoint = this._center.moveBy(toPoint.normalize().multiply(this._radius));

        // 计算参数 u
        const startAngle = Math.atan2(this.start.y - this._center.y, this.start.x - this._center.x);
        let endAngle = Math.atan2(this.end.y - this._center.y, this.end.x - this._center.x);
        const pointAngle = Math.atan2(projectedPoint.y - this._center.y, projectedPoint.x - this._center.x);

        // 确保角度在正确的范围内
        if (endAngle < startAngle) {
            endAngle += 2 * Math.PI;
        }

        let u = (pointAngle - startAngle) / (endAngle - startAngle);

        return { point: projectedPoint, u };
    }

    /**
     * @description 求两条曲线的交点
     * @param curve - 另一条曲线
     * @param between - 是否在两条曲线之间，默认 false
     * @return 交点数组
     */
    public getCrossPoints(curve: Curve2, between: boolean = false): Array<Point2> {
        if (curve.curveType === Curve2Type.Segment) {
            return this.getSegmentCrossPoints(curve as Segment2, between);
        } else if (curve.curveType === Curve2Type.Arc) {
            return this.getArcCrossPoints(curve as Arc2, between);
        }
        return [];
    }

    /**
     * @description 求圆弧与线段的交点
     */
    private getSegmentCrossPoints(segment: Segment2, between: boolean): Array<Point2> {
        // 计算线段的方向向量和圆心到起点的向量
        const lineDir = segment.start.vectorTo(segment.end);  // 修改为从起点指向终点
        const centerToStart = this._center.vectorTo(segment.start);

        // 计算二次方程的系数
        // at² + bt + c = 0，其中 t 是线段的参数
        const a = lineDir.coord.dot(lineDir.coord);  // 线段方向向量的平方长度
        const b = 2 * centerToStart.coord.dot(lineDir.coord);  // 2倍的圆心到起点向量与线段方向向量的点积
        const c = centerToStart.coord.dot(centerToStart.coord) - this._radius * this._radius;  // 圆心到起点距离的平方减去半径的平方

        // 求解二次方程
        const discriminant = b * b - 4 * a * c;
        if (discriminant < -Precision.EPSILON5) {
            return []; // 无交点
        }

        const points: Point2[] = [];

        if (Math.abs(discriminant) <= Precision.EPSILON5) {
            // 一个交点（相切）
            const t = -b / (2 * a);
            if (!between || (t >= 0 && t <= 1)) {
                const point = new Point2(
                    segment.start.x + t * lineDir.x,
                    segment.start.y + t * lineDir.y
                );
                if (this.containPoint(point)) {
                    points.push(point);
                }
            }
        } else {
            // 两个交点
            const sqrtDiscriminant = Math.sqrt(discriminant);
            const t1 = (-b - sqrtDiscriminant) / (2 * a);  // 修改为减号在前
            const t2 = (-b + sqrtDiscriminant) / (2 * a);  // 修改为加号在后

            if (!between || (t1 >= 0 && t1 <= 1)) {
                const point1 = new Point2(
                    segment.start.x + t1 * lineDir.x,
                    segment.start.y + t1 * lineDir.y
                );
                if (this.containPoint(point1)) {
                    points.push(point1);
                }
            }

            if (!between || (t2 >= 0 && t2 <= 1)) {
                const point2 = new Point2(
                    segment.start.x + t2 * lineDir.x,
                    segment.start.y + t2 * lineDir.y
                );
                if (this.containPoint(point2)) {
                    points.push(point2);
                }
            }
        }

        return points;
    }

    /**
     * @description 求两个圆弧的交点
     */
    private getArcCrossPoints(other: Arc2, between: boolean): Array<Point2> {
        // 计算圆心距离
        const centerDist = this._center.distance(other.center);
        const radiusSum = this._radius + other.radius;
        const radiusDiff = Math.abs(this._radius - other.radius);

        // 判断两圆的位置关系
        if (centerDist > radiusSum + Precision.EPSILON5) {
            return []; // 外离
        }
        if (centerDist < radiusDiff - Precision.EPSILON5) {
            return []; // 内含
        }

        // 使用余弦定理计算交点
        const cosTheta = (this._radius * this._radius + centerDist * centerDist -
            other.radius * other.radius) / (2 * this._radius * centerDist);

        // 处理数值误差，确保 cosTheta 在 [-1, 1] 范围内
        if (Math.abs(cosTheta) > 1 + Precision.EPSILON5) {
            return [];
        }

        // 如果是相切（两圆心距离等于半径之和）
        if (Math.abs(centerDist - radiusSum) < Precision.EPSILON5) {
            // 计算相切点
            const baseAngle = Math.atan2(other.center.y - this._center.y,
                other.center.x - this._center.x);
            const point = new Point2(
                this._center.x + this._radius * Math.cos(baseAngle),
                this._center.y + this._radius * Math.sin(baseAngle)
            );

            // 检查点是否在两个圆弧的角度范围内
            if (!between || (this.containPoint(point) && other.containPoint(point))) {
                return [point];
            }
            return [];
        }

        // 计算交点
        const baseAngle = Math.atan2(other.center.y - this._center.y,
            other.center.x - this._center.x);
        const deltaAngle = Math.acos(Math.max(-1, Math.min(1, cosTheta)));

        // 计算两个可能的交点
        const points: Point2[] = [];
        const angles = [baseAngle + deltaAngle, baseAngle - deltaAngle];

        for (const theta of angles) {
            const point = new Point2(
                this._center.x + this._radius * Math.cos(theta),
                this._center.y + this._radius * Math.sin(theta)
            );

            // 检查点是否在两个圆弧上
            if (!between || (this.containPoint(point) && other.containPoint(point))) {
                // 检查是否已经有相同的点（避免重复）
                if (!points.some(p => p.equals(point))) {
                    points.push(point);
                }
            }
        }

        return points;
    }

    /**
     * @description 曲线细分
     * @param subdivide - 细分次数, 默认 30
     * @return 细分点数组
     */
    public tessellate(subdivide: number = 30): Array<Point2> {
        const points: Point2[] = [];
        for (let i = 0; i <= subdivide; i++) {
            const u = i / subdivide;
            points.push(this.d0(u));
        }
        return points;
    }

    /**
     * @description 获取点在圆弧上的参数
     * @param point - 点
     * @returns 参数值（0-1之间）
     * @throws 如果点不在圆弧上则抛出错误
     */
    public getParameter(point: Point2): number {
        // 1. 检查点是否在圆弧上
        if (!this.containPoint(point)) {
            return Precision.ERROR;
        }

        // 2. 计算点相对于圆心的角度
        let pointAngle = Math.atan2(point.y - this._center.y, point.x - this._center.x);

        // 3. 将角度标准化到 [0, 2π) 范围内
        if (pointAngle < 0) {
            pointAngle += 2 * Math.PI;
        }

        // 4. 将起始角度和终止角度也标准化到 [0, 2π) 范围内
        let startAngle = this._startAngle % (2 * Math.PI);
        let endAngle = this._endAngle % (2 * Math.PI);
        if (startAngle < 0) startAngle += 2 * Math.PI;
        if (endAngle < 0) endAngle += 2 * Math.PI;

        // 5. 处理跨越0度线的情况
        if (endAngle < startAngle) {
            endAngle += 2 * Math.PI;
            if (pointAngle < startAngle) {
                pointAngle += 2 * Math.PI;
            }
        }

        // 6. 检查点是否在角度范围内
        if (pointAngle < startAngle - Precision.EPSILON5 || pointAngle > endAngle + Precision.EPSILON5) {
            return Precision.ERROR;
        }

        // 7. 返回一化的参数值 (0-1)
        return (pointAngle - startAngle) / (endAngle - startAngle);
    }

    /**
    * @description 线段相交性接口
    * @param curve2 - 另一曲线
    * @return 相交类型和交点
    */
    public intersect(curve2: Curve2): { type: IntersectionType, intersectPoints: Array<Point2> } {
        // 获取交点
        const intersectPoints = this.getCrossPoints(curve2, true);

        if (curve2.curveType === Curve2Type.Segment) {
            // 与线段的相交判断
            const segment = curve2 as Segment2;
            const startOnArc = this.containPoint(segment.start);
            const endOnArc = this.containPoint(segment.end);

            if (intersectPoints.length === 0) {
                // 检查线段是否完全在圆弧上
                if (startOnArc && endOnArc) {
                    // 线段是圆弧的子集
                    return {
                        type: IntersectionType.subSet,
                        intersectPoints: [segment.start, segment.end]
                    };
                }
                return {
                    type: IntersectionType.disjoint,
                    intersectPoints: []
                };
            } else {
                // 一个或两个交点都视为相交
                return {
                    type: IntersectionType.intersect,
                    intersectPoints: intersectPoints
                };
            }
        } else if (curve2.curveType === Curve2Type.Arc) {
            // 与圆弧的相交判断
            const arc2 = curve2 as Arc2;
            
            // 检查两个圆弧是否完全重合
            if (this._center.equals(arc2.center) && 
                Math.abs(this._radius - arc2.radius) < Precision.EPSILON5) {
                
                // 标准化角度到 [0, 2π) 范围
                const normalizeAngle = (angle: number): number => {
                    let result = angle % (2 * Math.PI);
                    if (result < 0) result += 2 * Math.PI;
                    return result;
                };
                
                const thisStart = normalizeAngle(this._startAngle);
                const thisEnd = normalizeAngle(this._endAngle);
                const arc2Start = normalizeAngle(arc2.startAngle);
                const arc2End = normalizeAngle(arc2.endAngle);
                
                // 完全相同
                if (Math.abs(thisStart - arc2Start) < Precision.EPSILON5 && 
                    Math.abs(thisEnd - arc2End) < Precision.EPSILON5) {
                    return {
                        type: IntersectionType.equal,
                        intersectPoints: [this.start, this.end]
                    };
                }
                
                // 检查包含关系
                const isThisContainsArc2 = this.containPoint(arc2.start) && this.containPoint(arc2.end);
                const isArc2ContainsThis = arc2.containPoint(this.start) && arc2.containPoint(this.end);
                
                if (isThisContainsArc2) {
                    return {
                        type: IntersectionType.superSet,
                        intersectPoints: []
                    };
                } else if (isArc2ContainsThis) {
                    return {
                        type: IntersectionType.subSet,
                        intersectPoints: []
                    };
                }
                
                // 同圆上的重叠
                // 找出重叠部分的端点
                const points = [this.start, this.end, arc2.start, arc2.end];
                const angles = points.map(p => {
                    let angle = Math.atan2(p.y - this._center.y, p.x - this._center.x);
                    if (angle < 0) angle += 2 * Math.PI;
                    return { point: p, angle: angle };
                });
                
                // 按角度排序
                angles.sort((a, b) => a.angle - b.angle);
                
                // 取中间两个点作为重叠部分的端点
                const overlapPoints = [angles[1].point, angles[2].point];
                
                return {
                    type: IntersectionType.overlap,
                    intersectPoints: overlapPoints
                };
            }
            
            if (intersectPoints.length === 0) {
                return {
                    type: IntersectionType.disjoint,
                    intersectPoints: []
                };
            } else {
                // 一个或两个交点都视为相交
                return {
                    type: IntersectionType.intersect,
                    intersectPoints: intersectPoints
                };
            }
        }

        // 其他类型的曲线
        return {
            type: IntersectionType.disjoint,
            intersectPoints: []
        };
    }


    /**
     * 获取曲线在指定参数位置的切向量
     * @param t 参数值
     * @returns 切向量
     */
    public tangentAt(t: number): Vector2 {
        const { tangent } = this.d1(t);
        return tangent;
    }

    /**
     * 获取曲线在指定参数位置的曲率
     * @param t 参数值
     * @returns 曲率值
     */
    public curvatureAt(t: number): number {
        // 圆弧的曲率是常数，等于 1/半径
        return 1 / this._radius;
    }

    /**
     * @description 判断是否是闭合曲线
     */
    public isClosed(): boolean {
        return Math.abs(this._endAngle - this._startAngle - 2 * Math.PI) < 1e-6;
    }
}